package cn.tang.tframe.common.utils.reflect;

import cn.tang.tframe.common.utils.DateUtils;
import cn.tang.tframe.common.utils.LRUCache;
import cn.tang.tframe.common.utils.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import static java.util.Locale.ENGLISH;

/**
 * 反射包装
 *
 * @author tanghc
 * @since 2015/11/6.
 */
public class ReflectionWrap {

	Logger logger = LoggerFactory.getLogger(ReflectionWrap.class);
	private final static ReflectionWrap instance = new ReflectionWrap();
	private final Map<Class<?>, Map<String, Field>> FIELD_CACHED = new LRUCache<Class<?>, Map<String, Field>>();
	private final Map<Class<?>, Map<String, Method>> CONVERT_METHOD_CACHED = new LRUCache<Class<?>, Map<String, Method>>();
	private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
	private final Lock writeLock = readWriteLock.writeLock();
	private final Lock writeMethodLock = readWriteLock.writeLock();
	static final String GET_PREFIX = "get";
	static final String IS_PREFIX = "is";
	/**
	 * 转换bean属性类型设置
	 */
	private boolean NULL_TO_EMPTY = true;
	private boolean DATE_TO_LONG = true;
	private boolean RECRU = true;
	private final String datePatten = "yyyy-MM-dd HH:mm:ss";
	private DateFormat dateFormat = new SimpleDateFormat(datePatten);

	public static final ReflectionWrap getInstance() {
		return instance;
	}

	private final PropertyConvertor defaultConvertor = new PropertyConvertor() {
		@Override
		public Object convert(String filedName, Method readMethod, Object methodReturn) {
			// null处理
			if (null == methodReturn) {
				if (NULL_TO_EMPTY) {
					return "";
				} else {
					return methodReturn;
				}
			}
			Class methodType = methodReturn.getClass();
			// Date类型处理
			if (methodType.equals(Date.class)) {
				Date date = (Date) methodReturn;
				if (DATE_TO_LONG) {
					return date.getTime();
				} else if (null != dateFormat) {
					try {
						return dateFormat.format(date);
					} catch (Exception e) {
						logger.error("reflectionwrap format date fail: dataformat={},date={}", new Object[] {
								dateFormat, date });
					}
					return "";
				} else {
					return date.toString();
				}
			}
			// 枚举类型处理
			if (methodType.isEnum()) {
				Enum enumVal = (Enum) methodReturn;
				return enumVal.name();
			}
			// 自定义类型递归处理
			if (!isJavaClass(methodType) && RECRU) {
				return ReflectionWrap.this.convertBean(methodReturn);
			}
			return methodReturn;
		}
	};

	public Map<String, Field> getFields(Class<?> clazz) {
		writeLock.lock();
		try {
			if (!FIELD_CACHED.containsKey(clazz)) {
				fillFields(clazz);
			}
			return FIELD_CACHED.get(clazz);
		} finally {
			writeLock.unlock();
		}
	}

	public Map<String, Method> getConvertMethods(Class<?> clazz) {
		writeMethodLock.lock();
		try {
			if (!CONVERT_METHOD_CACHED.containsKey(clazz)) {
				fillConvertMethods(clazz);
			}
			return CONVERT_METHOD_CACHED.get(clazz);
		} finally {
			writeMethodLock.unlock();
		}
	}

	/**
	 *
	 * @param source
	 * @param target
	 * @param onlyNotNull
	 *            是否只拷贝非空属性
	 */
	public void copyBeanByField(final Object source, final Object target, final Boolean onlyNotNull,
			String[] ignoreProperties) {
		final Class<?> sourceClazz = source.getClass();
		final Class<?> targetClazz = target.getClass();

		Map<String, Field> sourceFields = getFields(sourceClazz);
		Map<String, Field> targetFields = getFields(targetClazz);

		List<String> fileds = (null == ignoreProperties ? null : Arrays.asList(ignoreProperties));
		for (Map.Entry<String, Field> entry : sourceFields.entrySet()) {
			String fieldName = entry.getKey();
			Field sourceField = entry.getValue();
			Field targetField = targetFields.get(fieldName);
			if (targetField == null) {
				continue;
			}
			if (fileds != null && fileds.contains(fieldName)) {
				continue;
			}
			sourceField.setAccessible(true);
			targetField.setAccessible(true);
			Object sourceValue = invokeField(sourceField, source);
			if (onlyNotNull) {
				if (null != sourceValue) {
					setField(targetField, target, sourceValue);
				}
			} else {
				setField(targetField, target, sourceValue);
			}
		}
	}

	/**
	 * 默认只拷贝非空属性
	 * 
	 * @param source
	 * @param target
	 */
	public void copyBeanByField(final Object source, final Object target) {
		copyBeanByField(source, target, true, null);
	}

	public void setField(Field field, Object target, Object value) {
		try {
			field.setAccessible(true);
			field.set(target, value);
		} catch (Exception e) {
			// ignore
		}
	}

	public Object invokeField(Field field, Object target) {
		try {
			field.setAccessible(true);
			return field.get(target);
		} catch (Exception e) {
			// ignore
		}
		return null;
	}

	private void fillFields(Class<?> clazz) {
		final Map<String, Field> fields = new HashMap<String, Field>();
		FIELD_CACHED.put(clazz, fields);
		ReflectionUtils.doWithFields(clazz, new ReflectionUtils.FieldCallback() {
			@Override
			public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
				String fieldName = field.getName();
				if (!fields.containsKey(fieldName)) {
					fields.put(fieldName, field);
				}
			}
		});
	}

	private void fillConvertMethods(Class<?> clazz) {
		final Map<String, Method> methods = new HashMap<String, Method>();
		final Map<String, String> ignorPropertyReadMethod = new HashMap<String, String>();
		Map<String, Field> fields = this.getFields(clazz);
		if (null != fields) {
			for (Map.Entry<String, Field> entry : fields.entrySet()) {
				Field field = entry.getValue();
				// PropertyIgnor的feild对应的get/is方法过滤掉
				ConvertIgnor propertyIgnor = field.getAnnotation(ConvertIgnor.class);
				if (null != propertyIgnor) {
					String fieldName = field.getName();
					String readMethodName = null;
					Class type = field.getType();
					if (type == boolean.class || type == null) {
						readMethodName = IS_PREFIX + capitalize(fieldName);
					} else {
						readMethodName = GET_PREFIX + capitalize(fieldName);
					}
					ignorPropertyReadMethod.put(readMethodName, readMethodName);
				}
			}
		}
		ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() {
			@Override
			public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
				String name = method.getName();
				methods.put(readMethodToFieldName(method), method);
			}
		}, new ReflectionUtils.MethodFilter() {
			@Override
			public boolean matches(Method method) {
				// public的方法
				if (!Modifier.isPublic(method.getModifiers())) {
					return false;
				}
				String name = method.getName();
				if (null == name || (!name.startsWith(IS_PREFIX) && !name.startsWith(GET_PREFIX))) {
					return false;
				}
				Class<?>[] parameterTypes = method.getParameterTypes();
				// 只处理getxxx/isxxx方法中，无传入参数的方法
				if (null != parameterTypes && parameterTypes.length > 0) {
					return false;
				}
				// PropertyIgnor过滤掉
				ConvertIgnor propertyIgnor = method.getAnnotation(ConvertIgnor.class);
				if (null != propertyIgnor) {
					return false;
				}
				// 属性上注解了 PropertyIgnor过滤掉
				if (null != ignorPropertyReadMethod.get(method.getName())) {
					return false;
				}
				// class类型的数据过滤掉
				if (method.getReturnType().equals(Class.class)) {
					return false;
				}
				return true;
			}
		});
		CONVERT_METHOD_CACHED.put(clazz, methods);
	}

	/**
	 * 将一个 对象转化为一个 Map
	 * 
	 * @return
	 */
	public Map<String, Object> convertBean(Object bean) {
		return convertBean(bean, null);
	}

	/**
	 * 将一个 对象转化为一个 Map
	 * 
	 * @param bean
	 *            需要转换的对象
	 * @param convertor
	 *            对某些列进行特殊处理
	 * @return
	 */
	public Map<String, Object> convertBean(Object bean, PropertyConvertor convertor) {
		if (null == bean) {
			return Collections.emptyMap();
		}
		Class type = bean.getClass();
		Map<String, Object> returnMap = new HashMap<String, Object>();
		Map<String, Method> convertMethods = getConvertMethods(type);
		if (null != convertMethods) {
			for (Map.Entry<String, Method> entry : convertMethods.entrySet()) {
				String fieldName = entry.getKey();
				Method method = entry.getValue();
				Class<?> returnType = method.getReturnType();
				Object returnValue = ReflectionUtils.invokeMethod(method, bean);
				if (null != convertor && null != returnValue) {
					try {
						returnValue = convertor.convert(fieldName, method, returnValue);
					} catch (ConvertIgnorRuntimeException e) {
						logger.debug("custom PropertyConvertor ignore: method={},fieldName={}", new Object[] { method,
								fieldName });
					} catch (Exception e) {
						logger.error("custom PropertyConvertor exception: method={},fieldName={}", new Object[] {
								method, fieldName, e });
					}
				} else {
					returnValue = defaultConvertor.convert(fieldName, method, returnValue);
				}
				if (null != returnValue) {
					returnMap.put(fieldName, returnValue);
				}
			}
		}
		return returnMap;
	}

	public <E> E convertMap(Map<String, Object> map, Class<E> clazz, PropertyReflect<E> propertyReflect) {
		if (null == map || null == clazz) {
			return null;
		}
		E entity = null;
		try {
			if (null == propertyReflect) {
				propertyReflect = new PropertyReflect<E>() {
					@Override
					public void reflect(String filedName, Field field, Object bean, Object value) {
						Class valueClass = value.getClass();
						Class fieldClass = field.getType();
						if (fieldClass.isAssignableFrom(valueClass)) {
							setField(field, bean, value);
						} else if (fieldClass.isAssignableFrom(Date.class)) {
							if (value instanceof Number) {
								Long aLong = new Long(value.toString());
								if (null != aLong) {
									Date date = new Date(aLong);
									setField(field, bean, date);
								}
							} else if (value instanceof String) {
								setField(field, bean, DateUtils.parse(value.toString(), datePatten));
							}
						} else if (fieldClass.isEnum()) {
							if (value instanceof String) {
								Enum anEnum = Enum.valueOf(fieldClass, value.toString());
								setField(field, bean, anEnum);
							}
						} else if (value instanceof Map && !isJavaClass(fieldClass) && RECRU) {
							try {
								setField(field, bean,
										ReflectionWrap.this.convertMap((Map<String, Object>) value, fieldClass, null));
							} catch (Exception e) {
								logger.error("recru convertMap exception: value={}", value);
							}
						}
					}
				};
			}
			entity = clazz.newInstance();
			Map<String, Field> fields = this.getFields(clazz);
			if (null != fields) {
				for (Map.Entry<String, Field> entry : fields.entrySet()) {
					Field field = entry.getValue();
					String fieldName = entry.getKey();
					Object value = map.get(fieldName);
					if (value == null || StringUtils.isBlank(value.toString())) {
						continue;
					}
					propertyReflect.reflect(fieldName, field, entity, value);
				}
			}
		} catch (Exception e) {
			logger.error("convertMap excpetion: map={},clazz={}", map, clazz, e);
		}
		return entity;
	}

	/**
	 * Returns a String which capitalizes the first letter of the string.
	 */
	public static String capitalize(String name) {
		if (name == null || name.length() == 0) {
			return name;
		}
		return name.substring(0, 1).toUpperCase(ENGLISH) + name.substring(1);
	}

	/**
	 * Returns a String which capitalizes the first letter of the string.
	 */
	public static String uncapitalize(String name, String prefix) {
		if (name == null || name.length() == 0) {
			return null;
		}
		if (null != prefix && prefix.length() > 0) {
			if (!name.startsWith(prefix)) {
				return null;
			}
			name = name.substring(prefix.length());
		}
		if (name == null || name.length() == 0) {
			return null;
		}
		return name.substring(0, 1).toLowerCase(ENGLISH) + name.substring(1);
	}

	/**
	 * Returns a String which capitalizes the first letter of the string.
	 */
	public static String readMethodToFieldName(Method method) {
		if (null == method) {
			return null;
		}
		String name = method.getName();
		if (name.startsWith(IS_PREFIX)) {
			return uncapitalize(name, IS_PREFIX);
		} else if (name.startsWith(GET_PREFIX)) {
			return uncapitalize(name, GET_PREFIX);
		}
		return null;
	}

	public boolean isNULL_TO_EMPTY() {
		return NULL_TO_EMPTY;
	}

	public void setNULL_TO_EMPTY(boolean NULL_TO_EMPTY) {
		this.NULL_TO_EMPTY = NULL_TO_EMPTY;
	}

	public boolean isDATE_TO_LONG() {
		return DATE_TO_LONG;
	}

	public void setDATE_TO_LONG(boolean DATE_TO_LONG) {
		this.DATE_TO_LONG = DATE_TO_LONG;
	}

	public DateFormat getDateFormat() {
		return dateFormat;
	}

	public void setDateFormat(DateFormat dateFormat) {
		this.dateFormat = dateFormat;
	}

	public static boolean isJavaClass(Class clz) {
		return clz != null && clz.getClassLoader() == null;
	}

	public boolean isRECRU() {
		return RECRU;
	}

	public void setRECRU(boolean RECRU) {
		this.RECRU = RECRU;
	}

	public static void main(String[] args) {
		ReflectionWrap instance = ReflectionWrap.getInstance();
	}
}
